<?php
declare(strict_types=1);
namespace App\Models\Arts;

use App\Master\Enum\RedisKeyEnum;
use App\Models\BaseModel;
use App\Utils\Common;
use App\Utils\RedisUtil;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Support\Facades\Cache;

class SmsCodeModel extends BaseModel
{
    use HasFactory;

    protected $table      = 'sms_code';
    public    $timestamps = false;
    protected int $is_status_search = 1;// 默认使用 status = 1 筛选
    protected int $is_delete_search = 1;// 默认使用 is_delete = 0 筛选

    /**
     * 默认查询字段
     *
     * @var array|string[]
     */
    public array $select = ['*'];

    /**
     * 校验验证码
     * @param string $mobile
     * @param string $code
     * @param string $event 事件
     * @param int $currentLimit 可重复校验次数
     * @param int $timeOut 指定时间内可重复校验（s）
     * @return bool
     */
    public function check(string $mobile, string $code, string $event = 'default', int $currentLimit = 5, int $timeOut = 300)
    {
        // 测试验证码
        if ($code == 1212) {
            return $this->success();
        }

        if (!RedisUtil::getInstance(RedisKeyEnum::SMS_MOBILE_CHECK, $mobile)->tryTimes($timeOut,$currentLimit)){
            return $this->error('校验过于频繁，已被锁定！');
        }

        if (!$info = self::query()->where(['mobile' => $mobile, 'event' => $event])->orderBy('id', 'desc')->first()) {
            return $this->error('验证码错误');
        }

        if ($info->created_at < time() - 600) {
            $this->flush($mobile, $event);
            return $this->error('验证码已过期');
        }

        if ($info->code != $code) {
            return $this->error('验证码错误！');
        }

        return $this->success();
    }

    /**
     * 发送短信
     * @param string $mobile
     * @param string $event
     * @param int $currentLimit
     * @param int $timeOut
     * @return bool
     */
    public function send(string $mobile, string $event = 'default', int $currentLimit = 5, int $timeOut = 300)
    {
        $code = (string)rand(1000, 9999);
        if (!$this->create_code($mobile, $code, $event, $currentLimit, $timeOut)) {
            return $this->error($this->getMessage());
        }
        //TODO 第三方发送短信

        return $this->success('发送成功', [
            'code' => $code
        ]);
    }

    /**
     * 创建验证码
     * @param string $mobile
     * @param string $code
     * @param string $event
     * @param int $currentLimit
     * @param int $timeOut
     * @return bool
     */
    private function create_code(string $mobile, string $code, string $event = 'default', int $currentLimit = 5, int $timeOut = 300)
    {
        $time  = time();
        //验证缓存，如存在则继续限制发送，时间设置 300s
        if (RedisUtil::getInstance(RedisKeyEnum::SEND_SMS_MOBILE, $mobile)->get()) {
            return $this->error('您的发送过于频繁!');
        }

        //5分钟之内 次数超过5次，限制发送，并记录缓存
        if (!RedisUtil::getInstance(RedisKeyEnum::SEND_SMS_TIMES, $mobile)->tryTimes($timeOut,$currentLimit)) {
            //每次锁定 递增 锁定时间，达到最大锁定次数则将当日无法发送
            $end_time = Common::todayTimeRemain();//设置次日凌晨过期
            if (!$times = RedisUtil::getInstance(RedisKeyEnum::SEND_SMS_TIMEOUT_TIMES, $mobile)->tryTimes($end_time, $currentLimit)) {
                $timeOut = $end_time;//如果达到最大锁定次数，则设置当日过期
            } else {
                $timeOut = $times * $timeOut;//如果未达到最大次数，则依次递增锁定时间

                // 如果最大次数过期时间 超过 当日时间，则直接设置当日过期
                if ($timeOut > $end_time) {
                    $timeOut = $end_time;
                }
            }

            RedisUtil::getInstance(RedisKeyEnum::SEND_SMS_MOBILE, $mobile)->setex('1', $timeOut);
            $minutes = $timeOut / 60;
            return $this->error("发送过于频繁，请{$minutes}分钟后再试");
        }

        $this->flush($mobile, $event);

        $data = [
            'event'      => $event,
            'mobile'     => $mobile,
            'code'       => $code,
            'created_at' => $time
        ];
        if (!self::query()->insert($data)) {
            return $this->error('发送失败');
        }

        return $this->success('发送成功');
    }


    /**
     * 清除短信
     * @param string $mobile
     * @param string $event
     * @return bool
     */
    public function flush(string $mobile, string $event = 'default')
    {
        self::query()->where(['mobile' => $mobile, 'event' => $event])->delete();
        return $this->success();
    }
}
